
Gaps are quite common in trajectories. For example, GPS tracks may contain gaps if moving objects enter tunnels where GPS reception is lost. In other use cases, moving objects may leave the observation area for longer time before returning and continuing their recorded track.
Depending on the use case, we therefore might want to split trajectories at observation gaps that exceed a certain minimum duration:
import pandas as pd
import geopandas as gpd
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import movingpandas as mpd
import warnings
warnings.filterwarnings('ignore')
print(f'MovingPandas version {mpd.__version__}')
MovingPandas version 0.8.rc1
gdf = read_file('../data/geolife_small.gpkg')
traj_collection = mpd.TrajectoryCollection(gdf, 'trajectory_id', t='t')
my_traj = traj_collection.trajectories[1]
print(my_traj)
Trajectory 2 (2009-06-29 07:02:25 to 2009-06-29 11:13:12) | Size: 897 | Length: 38764.6m Bounds: (116.319212, 39.971703, 116.592616, 40.082514) LINESTRING (116.590957 40.071961, 116.590905 40.072007, 116.590879 40.072027, 116.590915 40.072004,
my_traj.plot(linewidth=5.0, capstyle='round', column='speed', vmax=20)
<AxesSubplot:>
help(mpd.ObservationGapSplitter)
Help on class ObservationGapSplitter in module movingpandas.trajectory_splitter: class ObservationGapSplitter(TrajectorySplitter) | ObservationGapSplitter(traj) | | Split trajectories into subtrajectories whenever there is a gap in the observations. | | Parameters | ---------- | gap : datetime.timedelta | Time gap threshold | min_length : numeric | Desired minimum length of trajectories. Shorter trajectories are discarded. | (Length is calculated using CRS units, except if the CRS is geographic (e.g. EPSG:4326 WGS84) | then length is calculated in metres.) | | Examples | -------- | | >>> mpd.ObservationGapSplitter(traj).split(gap=timedelta(hours=1)) | | Method resolution order: | ObservationGapSplitter | TrajectorySplitter | builtins.object | | Methods inherited from TrajectorySplitter: | | __init__(self, traj) | Create TrajectoryGeneralizer | | Parameters | ---------- | traj : Trajectory or TrajectoryCollection | | split(self, **kwargs) | Split the input Trajectory/TrajectoryCollection. | | Parameters | ---------- | kwargs : any type | Split parameters, differs by splitter | | Returns | ------- | TrajectoryCollection | Split trajectories | | ---------------------------------------------------------------------- | Data descriptors inherited from TrajectorySplitter: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
Split the trajectory where then are no observations for at least two minutes:
split = mpd.ObservationGapSplitter(my_traj).split(gap=timedelta(minutes=2))
split
TrajectoryCollection with 5 trajectories
split.to_traj_gdf()
| id | start_t | end_t | geometry | |
|---|---|---|---|---|
| 0 | 2_0 | 2009-06-29 07:02:25 | 2009-06-29 07:13:55 | LINESTRING (116.59096 40.07196, 116.59091 40.0... |
| 1 | 2_1 | 2009-06-29 07:16:55 | 2009-06-29 07:17:05 | LINESTRING (116.58690 40.07961, 116.58689 40.0... |
| 2 | 2_2 | 2009-06-29 07:29:35 | 2009-06-29 08:20:15 | LINESTRING (116.58703 40.07951, 116.58704 40.0... |
| 3 | 2_3 | 2009-06-29 10:57:17 | 2009-06-29 11:06:52 | LINESTRING (116.31970 40.00751, 116.31971 40.0... |
| 4 | 2_4 | 2009-06-29 11:09:12 | 2009-06-29 11:10:07 | LINESTRING (116.32636 40.00025, 116.32349 40.0... |
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle='round', column='speed', vmax=20)
help(mpd.StopSplitter)
Help on class StopSplitter in module movingpandas.trajectory_splitter: class StopSplitter(TrajectorySplitter) | StopSplitter(traj) | | Split trajectories at detected stops. | A stop is detected if the movement stays within an area of specified size for at least the specified duration. | | Parameters | ---------- | max_diameter : float | Maximum diameter for stop detection | min_duration : datetime.timedelta | Minimum stop duration | min_length : numeric | Desired minimum length of trajectories. Shorter trajectories are discarded. | (Length is calculated using CRS units, except if the CRS is geographic (e.g. EPSG:4326 WGS84) | then length is calculated in metres.) | | Examples | -------- | | >>> mpd.StopSplitter(traj).split(max_diameter=30, min_duration=timedelta(seconds=60)) | | Method resolution order: | StopSplitter | TrajectorySplitter | builtins.object | | Static methods defined here: | | get_time_ranges_between_stops(traj, stop_ranges) | | ---------------------------------------------------------------------- | Methods inherited from TrajectorySplitter: | | __init__(self, traj) | Create TrajectoryGeneralizer | | Parameters | ---------- | traj : Trajectory or TrajectoryCollection | | split(self, **kwargs) | Split the input Trajectory/TrajectoryCollection. | | Parameters | ---------- | kwargs : any type | Split parameters, differs by splitter | | Returns | ------- | TrajectoryCollection | Split trajectories | | ---------------------------------------------------------------------- | Data descriptors inherited from TrajectorySplitter: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
Split the trajectory where observations stay within 30 meters for at least 1 minute. Discard created trajectories that are shorter than 500 meters long:
split = mpd.StopSplitter(my_traj).split(max_diameter=30, min_duration=timedelta(minutes=1), min_length=500)
split
TrajectoryCollection with 4 trajectories
split.to_traj_gdf()
| id | start_t | end_t | geometry | |
|---|---|---|---|---|
| 0 | 2_2009-06-29 07:06:55 | 2009-06-29 07:06:55 | 2009-06-29 07:12:25 | LINESTRING (116.59258 40.07420, 116.59254 40.0... |
| 1 | 2_2009-06-29 07:29:35 | 2009-06-29 07:29:35 | 2009-06-29 08:02:30 | LINESTRING (116.58703 40.07951, 116.58704 40.0... |
| 2 | 2_2009-06-29 08:07:00 | 2009-06-29 08:07:00 | 2009-06-29 11:04:22 | LINESTRING (116.32328 39.98481, 116.32345 39.9... |
| 3 | 2_2009-06-29 11:06:12 | 2009-06-29 11:06:12 | 2009-06-29 11:13:12 | LINESTRING (116.32741 39.99990, 116.32744 39.9... |
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle='round', column='speed', vmax=20)
help(mpd.SpeedSplitter)
Help on class SpeedSplitter in module movingpandas.trajectory_splitter: class SpeedSplitter(TrajectorySplitter) | SpeedSplitter(traj) | | Split trajectories if there are no speed measurements above the speed limit for the specified duration. | | Parameters | ---------- | speed : float | Speed limit | duration : datetime.timedelta | Minimum stop duration | min_length : numeric | Desired minimum length of trajectories. Shorter trajectories are discarded. | (Length is calculated using CRS units, except if the CRS is geographic (e.g. EPSG:4326 WGS84) | then length is calculated in metres.) | max_speed: float | Max speed limit | (Speed is calculated as CRS units per second, except if the CRS is geographic (e.g. EPSG:4326 WGS84) | then speed is calculated in meters per second.) | | Examples | -------- | | >>> mpd.SpeedSplitter(traj).split(speed=10, duration=timedelta(minutes=5)) | | Method resolution order: | SpeedSplitter | TrajectorySplitter | builtins.object | | Methods inherited from TrajectorySplitter: | | __init__(self, traj) | Create TrajectoryGeneralizer | | Parameters | ---------- | traj : Trajectory or TrajectoryCollection | | split(self, **kwargs) | Split the input Trajectory/TrajectoryCollection. | | Parameters | ---------- | kwargs : any type | Split parameters, differs by splitter | | Returns | ------- | TrajectoryCollection | Split trajectories | | ---------------------------------------------------------------------- | Data descriptors inherited from TrajectorySplitter: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined)
Split the trajectory where the speed is below one meters per second for at least five minutes:
split = mpd.SpeedSplitter(my_traj).split(speed=1, duration=timedelta(minutes=5))
split
TrajectoryCollection with 3 trajectories
split.to_traj_gdf()
| id | start_t | end_t | geometry | |
|---|---|---|---|---|
| 0 | 2_0 | 2009-06-29 07:02:25 | 2009-06-29 07:17:05 | LINESTRING (116.59096 40.07196, 116.59091 40.0... |
| 1 | 2_1 | 2009-06-29 07:29:55 | 2009-06-29 08:20:15 | LINESTRING (116.58704 40.07947, 116.58705 40.0... |
| 2 | 2_2 | 2009-06-29 10:57:22 | 2009-06-29 11:10:07 | LINESTRING (116.31971 40.00759, 116.31964 40.0... |
fig, axes = plt.subplots(nrows=1, ncols=len(split), figsize=(19,4))
for i, traj in enumerate(split):
traj.plot(ax=axes[i], linewidth=5.0, capstyle='round', column='speed', vmax=20)